package com.uxsino.simo.query;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;

import org.apache.commons.collections4.keyvalue.MultiKey;

import com.alibaba.fastjson.annotation.JSONField;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.uxsino.commons.utils.config.ConfigProp;
import com.uxsino.reactorq.model.INDICATOR_TYPE;
import com.uxsino.reactorq.model.IndicatorValue;
import com.uxsino.simo.indicator.Indicator;
import com.uxsino.simo.indicator.ListIndicator;
import com.uxsino.simo.networkentity.EntityInfo;

public class IndicatorRefQueryParameter extends AbstractQueryParameter {
    // private Logger logger = LoggerFactory.getLogger(this.getClass());

    private Indicator refIndicator;

    @JsonProperty("field")
    @JSONField(name = "field")
    private String fieldName;

    @ConfigProp(name = "force_new")
    public boolean forceNewQuery = true;

    public void setIndicator(Indicator ind, String fieldName) {
        refIndicator = ind;
        this.fieldName = fieldName;
    }

    @Override
    public INDICATOR_TYPE getType() {
        return refIndicator == null ? INDICATOR_TYPE.UNKNOWN : refIndicator.getIndicatorType();
    }

    @JsonProperty("indicator")
    @JSONField(name = "indicator")
    public String getIndicatorName() {
        return refIndicator == null ? null : refIndicator.name;
    }

    @JsonIgnore
    @JSONField(serialize = false)
    public Indicator getIndicator() {
        return refIndicator;
    }

    // @Override
    // public INDICATOR_QUERY_STATUS getQueryStatus(QueryContext ctxt, EntityInfo ne) {
    // return ctxt.getIndicatorQueryStatus(ne.id, refIndicator.name);
    // }

    @Override
    public Object getValue(QueryContext ctxt, EntityInfo ne) {
        if (refIndicator == null)
            return null;

        IndicatorValue r = ctxt.getDepo().getIndicatorValue(ne.id, refIndicator);
        // logger.info("***********************ne.id:{}, refIndicator:{}, IndicatorValue:{}", ne.id, refIndicator, r);

        if (r == null || r.value == null)
            return null;

        if (getType() == INDICATOR_TYPE.LIST) {
            @SuppressWarnings("unchecked")
            List<Map<String, Object>> lv = (List<Map<String, Object>>) r.value;
            ArrayList<Object> fieldValues = new ArrayList<>();
            for (Map<String, Object> entry : lv) {

                Object fv = entry.get(fieldName);

                // logger.info("************List fieldName:{},fieldValue:{}", fieldName, fv);
                fieldValues.add(fv);
            }

            // logger.info("indicatorRef: fieldValueList: {}", fieldValues);
            return fieldValues;
        } else if (getType() == INDICATOR_TYPE.COMPOUND) {
            @SuppressWarnings("unchecked")
            Map<String, Object> cv = (Map<String, Object>) r.value;

            // logger.info("***********compound fieldName:{}. fieldValue:{}", fieldName, cv.get(fieldName));
            return cv.get(fieldName);
        } else {
            return r.value;
        }
    }

    /***
     * 
     * @param ctxt
     * @param ne
     * @param addValue
     *            void addValue(Object key, Object value)
     */
    @Override
    public void getV(EntityInfo entity, QueryContext ctxt, BiConsumer<MultiKey<String>, Object> addValue) {
        if (refIndicator == null)
            return;
        IndicatorValue r = ctxt.getDepo().getIndicatorValue(entity.id, refIndicator);

        if (r == null || r.value == null)
            return;

        if (getType() == INDICATOR_TYPE.LIST) {
            ListIndicator li = (ListIndicator) refIndicator;
            String[] keyFieldNames = li.getKeyFieldNames();
            @SuppressWarnings("unchecked")
            List<Map<String, Object>> lv = (List<Map<String, Object>>) r.value;
            for (Map<String, Object> entry : lv) {
                MultiKey<String> fk = getKeyValue(keyFieldNames, entry);
                Object fv = entry.get(fieldName);
                addValue.accept(fk, fv);
            }
        } else if (getType() == INDICATOR_TYPE.COMPOUND) {
            @SuppressWarnings("unchecked")
            Map<String, Object> cv = (Map<String, Object>) r.value;
            addValue.accept(null, cv.get(fieldName));
        }
    }

    /***
     * get a string representing key values
     * @return
     */
    private MultiKey<String> getKeyValue(String[] keyFieldNames, Map<String, Object> row) {
        if (keyFieldNames == null)
            return new MultiKey<String>(new String[0]);

        String[] r = new String[keyFieldNames.length];
        for (int i = 0; i < r.length; i++) {
            Object keyValue = row.get(keyFieldNames[i]);
            if (keyValue == null) {
                r[i] = null;
            } else {
                r[i] = keyValue.toString();
            }
        }
        return new MultiKey<String>(r);
    }

    /***
     * 
     * used in parameter expanding. two parameter with list value will be
     * expanded into Cartesian aggregation, unless they have the same key name.
     * for example: {1,2,3} - {a,b} --> {[1,a],[1,b],[2,a],[2,b],[3,a],[3,b]} if
     * they are marshaled with same key: {[k1,v1],[k2,v2]} - {[k1,w1],[k2,w2]}
     * --> {[k1,v1,w1],[k2,v2,w2]}
     * 
     * @return a string represents the key, null if not grouped.
     */
    @Override
    public String getGroupingKey() {
        if (getType() == INDICATOR_TYPE.LIST) {
            return "listind#" + refIndicator.name;
        }
        return null;
    }

}
